Skip to content

Map hostedtool.CodeInterpreter to the Anthropic code_execution tool and parse its result blocks - #637

Closed
PratikDhanave (PratikDhanave) wants to merge 5 commits into
microsoft:mainfrom
PratikDhanaveFork:anthropic-code-interpreter
Closed

Map hostedtool.CodeInterpreter to the Anthropic code_execution tool and parse its result blocks#637
PratikDhanave (PratikDhanave) wants to merge 5 commits into
microsoft:mainfrom
PratikDhanaveFork:anthropic-code-interpreter

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

Wire the hosted *hostedtool.CodeInterpreter marker into the Anthropic provider (provider/anthropicprovider/agent.go):

  • Request builder: add a tool-loop branch that maps *hostedtool.CodeInterpreter onto Anthropic's server-side code_execution tool (CodeExecutionTool20250825Param), appended to params.Tools.
  • Response parsing: add buildBlock cases for the server_tool_use (code_execution) and code_execution_tool_result blocks, surfacing them as structured message.CodeInterpreterToolCallContent (code as a text/x-python DataContent) and message.CodeInterpreterToolResultContent (stdout/stderr/error as TextContent, output files as HostedFileContent).

Why

This is the code-execution sibling of the existing web-search hosted-tool support. The OpenAI Responses provider already maps *hostedtool.CodeInterpreter (responses.go, case *hostedtool.CodeInterpreter) and emits the same structured CodeInterpreterToolCall/CodeInterpreterToolResult content. The Anthropic provider had no hosted-tool branch and no code_execution result parsing, so hosted code interpretation silently did nothing on Anthropic. This change brings Anthropic in line with the OpenAI/.NET structured mapping so the same agent code works across providers.

Tests

Added to the canonical agent_test.go (black-box, reusing the existing httptest harness):

  • TestCodeInterpreterToolMapsToCodeExecution asserts the request builder emits a code_execution / code_execution_20250825 tool in tools.
  • TestCodeInterpreterResultBlocksBecomeStructuredContent feeds server_tool_use + code_execution_tool_result blocks and asserts structured CodeInterpreterToolCallContent (decoded Python source) and CodeInterpreterToolResultContent (stdout + hosted file output) are produced.

go build ./..., go vet ./provider/anthropicprovider/..., and go test ./provider/anthropicprovider/... all pass.

Open design questions

  • Tool version: this pins CodeExecutionTool20250825Param. The SDK also exposes newer variants (20260120, 20260521); should the version be configurable (e.g. via AdditionalProperties) or track the latest?
  • Container / file inputs: hostedtool.CodeInterpreter.Inputs (hosted file IDs) are not yet forwarded — Anthropic's code_execution container model differs from OpenAI's. Follow-up if pre-seeding files is needed.
  • Streaming accumulation: non-streaming is fully mapped; in streaming the server_tool_use input arrives via input_json_delta and is not yet accumulated into the call's code block. Worth a follow-up if streaming code capture is required.
  • Encrypted results: encrypted_stdout (EncryptedCodeExecutionResultBlock) is not surfaced; only plaintext stdout/stderr and output files are mapped today.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 23, 2026
@github-actions

This comment has been minimized.

Wire the hosted *hostedtool.CodeInterpreter marker into the Anthropic
provider so it enables the server-side code_execution tool, and parse the
resulting server_tool_use / code_execution_tool_result response blocks into
structured message.CodeInterpreterToolCallContent and
CodeInterpreterToolResultContent. This mirrors the existing OpenAI Responses
provider mapping so hosted code interpretation behaves the same across
providers.
@github-actions

This comment has been minimized.

# Conflicts:
#	provider/anthropicprovider/agent.go
#	provider/anthropicprovider/agent_test.go
@github-actions

This comment has been minimized.

@PratikDhanave
PratikDhanave (PratikDhanave) marked this pull request as ready for review August 4, 2026 06:06
@PratikDhanave
PratikDhanave (PratikDhanave) requested a review from a team as a code owner August 4, 2026 06:06
Copilot AI lite review requested due to automatic review settings August 4, 2026 06:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds hosted code-interpreter support to the Anthropic provider by mapping the framework’s *hostedtool.CodeInterpreter marker to Anthropic’s server-side code_execution tool, and by parsing corresponding response blocks into the framework’s structured message.CodeInterpreterToolCallContent / message.CodeInterpreterToolResultContent content types.

Changes:

  • Map *hostedtool.CodeInterpreter to Anthropic code_execution_20250825 tool parameters in request construction.
  • Parse Anthropic server_tool_use (code_execution) blocks into CodeInterpreterToolCallContent with base64-encoded Python source (text/x-python).
  • Parse Anthropic code_execution_tool_result blocks into CodeInterpreterToolResultContent including stdout/stderr and hosted file outputs, with new black-box tests covering request/response behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
provider/anthropicprovider/agent.go Adds CodeInterpreter → Anthropic code_execution tool mapping and response block parsing for server-side code execution.
provider/anthropicprovider/agent_test.go Adds tests validating request tool emission and structured parsing of code execution call/result blocks.

Comment on lines +324 to +335
if res.Stderr != "" {
result.Outputs = append(result.Outputs, &message.TextContent{
Text: res.Stderr,
ContentHeader: message.ContentHeader{RawRepresentation: res},
})
}
if res.ErrorCode != "" {
result.Outputs = append(result.Outputs, &message.TextContent{
Text: string(res.ErrorCode),
ContentHeader: message.ContentHeader{RawRepresentation: res},
})
}
# Conflicts:
#	provider/anthropicprovider/agent_test.go
@github-actions github-actions Bot added area:provider Changes files in the provider area area:provider/anthropic Changes files in the provider / anthropic area size:large At most 300 changed lines across at most 10 files pending-auto-risk Automatic risk classification is in progress labels Aug 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Parity review: PR #637 — Anthropic code_execution hosted-tool support

This PR ports the hosted CodeInterpreter tool to the Anthropic provider. The general approach (mapping hostedtool.CodeInterpreterCodeExecutionTool20250825Param, parsing server_tool_use/code_execution_tool_result blocks into structured CodeInterpreterToolCallContent/CodeInterpreterToolResultContent) is correctly aligned with the Python implementation in python/packages/anthropic/agent_framework_anthropic/_chat_client.py.

Two cross-SDK divergences were found:

1. stderr and error_code mapped to TextContent instead of ErrorContent (actionable)

Python maps both stderr and error_code to Content.from_error() (→ ErrorContent). The Go implementation uses &message.TextContent{} for both. Callers that branch on content type to detect errors will behave differently across SDKs. See inline comment at line 333.

2. Tool-call input encoding (DataContent vs TextContent) (needs clarification)

Python wraps the raw input as TextContent(text=str(input)). Go extracts the "code" key and stores it base64-encoded as DataContent{MediaType: "text/x-python"}. This is more structured but is an intentional divergence. If deliberate, it should be documented. See inline comment at line 316.


The parity-approved label has been removed because there are two cross-SDK semantic differences that should be resolved or explicitly documented before the PR is considered parity-clean.

No exported Go API surface was added by this PR (the changed types CodeInterpreterToolCallContent / CodeInterpreterToolResultContent already existed); the public-api-change label is not warranted.

Generated by Go API Consistency Review Agent · sonnet46 · 48.8 AIC · ⌖ 5.81 AIC · ⊞ 6K ·

@github-actions github-actions Bot removed the parity-approved Go API consistency review found no parity issues label Aug 20, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by Go API Consistency Review Agent · sonnet46 · 48.8 AIC · ⌖ 5.81 AIC · ⊞ 6K

})
}
if res.Stderr != "" {
result.Outputs = append(result.Outputs, &message.TextContent{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity issue: stderr mapped to TextContent instead of ErrorContent

The upstream Python implementation (agent_framework_anthropic/_chat_client.py, case "code_execution_tool_result") maps stderr to Content.from_error(message=content_block.content.stderr), which produces an ErrorContent node. Here the Go implementation maps stderr to &message.TextContent{}. Callers that switch on content type (e.g., to distinguish diagnostic output from normal output) will behave differently across SDKs.

Suggestion: use &message.ErrorContent{Message: res.Stderr, ...} for stderr to match Python semantics. Similarly, error_code (line 339) maps to TextContent in Go but to Content.from_error() in Python — both should use ErrorContent.

Upstream reference: python/packages/anthropic/agent_framework_anthropic/_chat_client.py lines 1338–1356.

},
}
}
contents = append(contents, call)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity note: code_execution tool-call input encoding differs from Python

The Python implementation (_chat_client.py, line 1252–1265) wraps the raw input block as Content.from_text(text=str(content_block.input)) — a plain TextContent containing the string representation of the input dict.

This Go implementation extracts the "code" key from the JSON payload and stores it base64-encoded as DataContent with media_type: text/x-python. While arguably more structured (and analogous to how OpenAI Responses surfaces it), this is a deliberate cross-SDK divergence: a consumer inspecting CodeInterpreterToolCallContent.Inputs[0] will receive a DataContent in Go but a TextContent in Python.

If this encoding difference is intentional, please document it (e.g., in a CHANGELOG entry or an inline comment noting the divergence from Python). If it should align, switch to &message.TextContent{Text: code} or align the Python side to emit DataContent.

@github-actions github-actions Bot added failed-auto-risk Automatic risk classification was inconclusive or failed and removed pending-auto-risk Automatic risk classification is in progress labels Aug 20, 2026
# Conflicts:
#	provider/anthropicprovider/agent_test.go
@github-actions github-actions Bot added pending-auto-risk Automatic risk classification is in progress risk:medium Contained production impact requiring normal review depth and removed failed-auto-risk Automatic risk classification was inconclusive or failed pending-auto-risk Automatic risk classification is in progress labels Aug 22, 2026
@github-actions github-actions Bot added the public-api-change Pull Request changes public APIs label Aug 22, 2026
# Conflicts:
#	provider/anthropicprovider/agent_test.go
@github-actions github-actions Bot added pending-auto-risk Automatic risk classification is in progress risk:medium Contained production impact requiring normal review depth and removed risk:medium Contained production impact requiring normal review depth pending-auto-risk Automatic risk classification is in progress labels Aug 26, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • proxy.golang.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "proxy.golang.org"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · sonnet46 · 134.8 AIC · ⌖ 4.96 AIC · ⊞ 6.4K

// code_execution tool, mirroring the OpenAI Responses provider.
tools = append(tools, anthropic.ToolUnionParam{
OfCodeExecutionTool20250825: &anthropic.CodeExecutionTool20250825Param{},
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity finding — missing anthropic-beta: code-execution-2025-08-25 header

The upstream Python implementation (python/packages/anthropic/agent_framework_anthropic/_chat_client.py) unconditionally includes code-execution-2025-08-25 in its BETA_FLAGS list and routes every request through client.beta.messages.create(), which sends the anthropic-beta header automatically. Without that header the Anthropic Messages API will not recognise the code_execution_20250825 tool or return server_tool_use/code_execution_tool_result blocks.

The Go provider uses the stable client.Messages.New() path and currently sends no anthropic-beta header when *hostedtool.CodeInterpreter is present. The request will likely be rejected or silently ignored by the API.

Upstream evidence: python/packages/anthropic/agent_framework_anthropic/_chat_client.pyBETA_FLAGS = ["mcp-client-2025-04-04", "code-execution-2025-08-25"] and _prepare_betas() merges that list into every request.

Suggested resolution: when *hostedtool.CodeInterpreter is appended to tools, also inject option.WithHeader("anthropic-beta", "code-execution-2025-08-25") into the Messages.New() / Messages.NewStreaming() call, or propagate it via MessageNewParams / an equivalent per-request option so the required beta flag reaches the API.

Comment on lines +333 to +341
if res.Stderr != "" {
result.Outputs = append(result.Outputs, &message.TextContent{
Text: res.Stderr,
ContentHeader: message.ContentHeader{RawRepresentation: res},
})
}
if res.ErrorCode != "" {
result.Outputs = append(result.Outputs, &message.TextContent{
Text: string(res.ErrorCode),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity finding — stderr and error_code mapped as TextContent rather than as an error signal

The upstream Python implementation (python/packages/anthropic/agent_framework_anthropic/_chat_client.py, case "code_execution_tool_result") distinguishes between output and error semantics:

  • stderrContent.from_error(message=..., ...) (error-typed content)
  • BetaCodeExecutionToolResultError.error_code (the error-class result type) → Content.from_error(...)

The Go implementation maps both res.Stderr and res.ErrorCode to &message.TextContent{}, the same type used for stdout. Callers that inspect content type to detect execution failure will receive a plain text block instead of an error-typed content item, diverging from Python's semantic.

Note: res.ErrorCode in the Go SDK (CodeExecutionToolResultBlock.Content.ErrorCode) is a field on the success result type. Verify whether it carries the same semantics as Python's BetaCodeExecutionToolResultError; if so, it should also map to an error content type.

Upstream evidence: python/packages/anthropic/agent_framework_anthropic/_chat_client.pyContent.from_error(message=content_block.content.error_code, ...) for BetaCodeExecutionToolResultError, and Content.from_error(message=content_block.content.stderr, ...) for stderr.

Suggested resolution: Map res.Stderr to a message type carrying error semantics (or document the intentional divergence), and review whether res.ErrorCode represents an error-class result that should be surfaced as an error rather than plain text.

Comment on lines +310 to +313
call.Inputs = message.Contents{
&message.DataContent{
Data: base64.StdEncoding.EncodeToString([]byte(code)),
MediaType: "text/x-python",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity finding — tool-call Inputs shape diverges from Python: DataContent (base64 + MIME) vs TextContent (raw string)

The upstream Python implementation (python/packages/anthropic/agent_framework_anthropic/_chat_client.py, the server_tool_use branch for code_execution) maps the tool-call input as:

inputs=[
    Content.from_text(
        text=str(content_block.input),
        raw_representation=content_block,
    )
]

This produces a plain TextContent with the raw string representation of the input dict.

The Go implementation extracts the code field from the input JSON and wraps it in a *message.DataContent with MediaType: "text/x-python" and base64-encoded Data. This is a meaningfully different shape:

  • The Python Inputs[0] is a TextContent; the Go Inputs[0] is a DataContent.
  • The Python value is the full input dict as a string (e.g. {"code": "..."}); the Go value is only the extracted code, base64-encoded.

Callers that pattern-match on Inputs[0] type or decode the content will see different types and encoding across SDKs.

Upstream evidence: python/packages/anthropic/agent_framework_anthropic/_chat_client.pyContent.from_text(text=str(content_block.input), ...) in the server_tool_use / code_execution branch.

Suggested resolution: Align the Inputs element with the Python shape (a TextContent containing the extracted code as plain text), or document this intentional divergence. The DataContent + text/x-python approach is reasonable as an enhancement, but should be explicitly discussed with the cross-SDK design owners.

@qmuntal

Copy link
Copy Markdown
Member

Too many parity issues and open questions. This needs some human love. Closing, thanks anyway!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider/anthropic Changes files in the provider / anthropic area area:provider Changes files in the provider area public-api-change Pull Request changes public APIs risk:medium Contained production impact requiring normal review depth size:large At most 300 changed lines across at most 10 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants